package lesson.base.paramresolve;


import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.FileCleanerCleanup;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 从自研Forward框架中抽取出来的代码整合的一个工具类，仅用于学生们的Servlet练习项目。
 * 因组件缺失，这些代码做了很多删减和改动，未考虑性能和扩展性。
 * 想学习的同学可以向我要框架完整代码。
 */
public class ParamUtil {

    public static final char NESTED='.';
    public static final char BRACKET_START='[';
    public static final char BRACKET_END=']';
    public static final String EMPTY="";


    private static ServletContext servletContext = null;

    /**
     * 上传相关
     */
    private static DiskFileItemFactory factory = new DiskFileItemFactory();
    public static void setServletContext(ServletContext sc) {
        servletContext = sc;
        File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
        factory.setRepository(repository);
        factory.setFileCleaningTracker(FileCleanerCleanup.getFileCleaningTracker(servletContext));

    }



    private static final ConcurrentHashMap<Method, ParamDefinition[]> METHOD_PARAM = new ConcurrentHashMap<>(64);


    /**
     * 根据请求和方法，解析出参数
     * @param request
     * @param method
     * @return
     */
    public static Object[] getParamValues(HttpServletRequest request, Method method) {

        ParamDefinition[] params = getMethodParamDefinitions(method);
        Object[] paramValues = new Object[params.length];
        for (int i = 0; i < params.length; i++) {
            try {
                paramValues[i] = resolve(request, params[i]);
            } catch (Exception e) {
                throw new RuntimeException("参数" + params[i].getName() + "解析失败", e);
            }
        }
        return paramValues;

    }

    private static Object resolve(HttpServletRequest request, ParamDefinition param) {
        return ParamUtil.compose(param, getNormalParameterGroup(request).get(param.getName()));
    }


    /**
     * 将请求数据根据参数名进行分组
     * @return
     */
    private static Map<String, List<Map.Entry<String, String[]>>> getNormalParameterGroup(HttpServletRequest request) {
        Map<String, List<Map.Entry<String, String[]>>> normalParameterGroup = new HashMap<>();
            Map<String, String[]> parameterMap = getParamMap(request);
            if (parameterMap != null && parameterMap.isEmpty() == false) {
                Iterator<Map.Entry<String, String[]>> iterator = parameterMap.entrySet().iterator();
                String key;
                while (iterator.hasNext()) {
                    Map.Entry<String, String[]> item = iterator.next();
                    key = ParamUtil.clean(item.getKey());
                    List<Map.Entry<String, String[]>> groupItem = normalParameterGroup.get(key);
                    if (groupItem == null) {
                        groupItem = new ArrayList<Map.Entry<String, String[]>>();
                        normalParameterGroup.put(key, groupItem);
                    }
                    groupItem.add(item);
                }
            }
        return normalParameterGroup;
    }

    private static Map<String, String[]> getParamMap(HttpServletRequest request) {
        String contentType = request.getContentType();
        Map<String, String[]> map = new HashMap<>();
        map.putAll(request.getParameterMap());
        if(contentType != null && contentType.toLowerCase().contains("multipart/form-data")) {
            ServletFileUpload upload = new ServletFileUpload(factory);
            upload.setFileSizeMax(10000* 1000); //单位字节
            upload.setSizeMax(10000 * 1000); //单位字节
            try {
                List<FileItem> items = upload.parseRequest(request);
                String value;
                for (FileItem item : items) {
                    if (item.isFormField()) { //普通form表单字段
                        value = item.getString("utf-8");
                    } else { //上传的文件
//                        if (checkIsAllowUpload(item.getName()) == false) {
//                            throw new RuntimeException("不允许上传的文件类型");
//                        }
                        value = getTempPath((DiskFileItem)item);
                    }
                    String[] currentValue = map.get(item.getFieldName());
                    if (currentValue == null) {
                        map.put(item.getFieldName(), new String[]{value});
                    } else {
                        currentValue = Arrays.copyOf(currentValue, currentValue.length + 1);
                        currentValue[currentValue.length - 1] = value;
                        map.put(item.getFieldName(), currentValue);
                    }
                }
            } catch (FileUploadBase.FileSizeLimitExceededException e) {
                throw new RuntimeException("上传的单个文件不能超过xxK");
            } catch (FileUploadBase.SizeLimitExceededException e) {
                throw new RuntimeException("单次请求内容超出大小限制");
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return map;
    }


    /**
     * 获取文件的临时保存路径
     * @param item
     * @return
     */
    private static String getTempPath(DiskFileItem item) {
        if (item.getSize() == 0) return null;
        File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");

        String path = repository.toURI().getPath() + new Date().getTime() + "_" + new Random().nextInt(1000) + "_" + item.getName();
        File newFile = new File(path);
        if (item.isInMemory()) {
            try {
                item.write(newFile);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            File now = item.getStoreLocation();
            if (now == null) {
                throw new RuntimeException("无法保存文件到硬盘");
            }

            now.renameTo(newFile);
        }
        return path;
    }

    private static ParamDefinition[] getMethodParamDefinitions(Method method) {
        ParamDefinition[] paramDefinitions = METHOD_PARAM.get(method);
        if (paramDefinitions != null) {
            return paramDefinitions;
        }
        paramDefinitions = resolveParams(method.getParameters());
        METHOD_PARAM.putIfAbsent(method, paramDefinitions);
        return paramDefinitions;
    }

    /**
     * 解析方法参数
     * @param params
     * @return
     */
    private static ParamDefinition[] resolveParams(Parameter[] params) {
        ParamDefinition[] definitions = new ParamDefinition[params.length];
        for (int i = 0; i < params.length; i++) {
            Parameter param = params[i];
            definitions[i] = new ParamDefinition(param);
        }
        return definitions;
    }
    /**
     * 排除 . 或者 [ 的干扰
     * 如果是 aaa 返回 aaa
     * 如果是 aaa.b 或者 aaa[0]返回 aaa
     * 如果是 .b 或者 [b] 返回b
     * @param key
     * @return
     */
    public static String clean(String key) {
        if (StringKit.isBlank(key)) return key;
        int startIndex = -1;
        char c;
        char[] chars = key.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            c = chars[i];
            if (c != NESTED && c != BRACKET_START && c != BRACKET_END) {
                if (startIndex == -1) startIndex = i;
            }else{
                if (startIndex != -1) return key.substring(startIndex, i);
            }
        }
        if (startIndex > -1) return key.substring(startIndex);
        else return EMPTY;
    }




    /**
     * 将request传的数据组成对应参数对象
     * @param paramDefinition
     * @param paramGroupData
     * @return
     */
    public static Object compose(ParamDefinition paramDefinition, List<Map.Entry<String, String[]>> paramGroupData) {
        if (paramGroupData == null) {
            return ParamType.getDefaultValue(paramDefinition.getParameter().getType());
        } else {
            Object result = null;
            for (Map.Entry<String, String[]> item : paramGroupData) {
                result = processParamValue(result,
                        paramDefinition.getParamFeature(), item.getValue(),  item.getKey().substring(paramDefinition.getName().length()));
            }
            return result;
        }
    }


    /**
     * 转化请求参数
     * @param result
     * @param feature
     * @param paramValues
     * @param remainKey
     * @return
     */
    private static Object processParamValue(Object result, ParamDefinition.ParamFeature feature, String[] paramValues, String remainKey) {

        switch (feature.getParamType()) {
            case COMMON:
                return processCommonParamValue(feature, paramValues);
            case ARRAY:
                return processArrayParamValue(feature, paramValues, remainKey);
            case POJO:
                return processPojoParamValue(result, feature, paramValues, remainKey);
            case LIST:
                return processListParamValue(result, feature, paramValues, remainKey);
            case MAP:
                return processMapParamValue(result, feature, paramValues, remainKey);

        }


        return result;
    }


    /**
     *
     * @param feature
     * @param paramValues
     * @return
     */
    private static Object processCommonParamValue(ParamDefinition.ParamFeature feature, String[] paramValues) {
        //上传文件要特殊处理
        if (feature.getClazz() == UploadFile.class) {

            return convertToUploadFile(paramValues[0]);
        }
        return StringKit.convert(StringKit.join(",", paramValues ), feature.getClazz());
    }



    /**
     * 将临时文件转为UploadFile对象
     * @param tempPath
     * @return
     */
    private static UploadFile convertToUploadFile(String tempPath) {
        if (StringKit.isBlank(tempPath)) return null;
        File tempFile = new File(tempPath);
        if (tempFile.exists() == false) return null;
        tempPath = tempFile.getName();
        //临时文件地址，命名方式为 randomstr_uuid_randomstr_originalname
        int index = tempPath.indexOf("_");
        String randomStr = tempPath.substring(0, index);
        index = tempPath.indexOf("_"+randomStr+"_");
        String originalName =  tempPath.substring(index + randomStr.length() + 2);
        UploadFile file = new UploadFile();
        file.setOriginalName(originalName);
        String savePath = getFileSavePath(originalName);
        file.setRelativePath(savePath);
        file.setSize(tempFile.length());
        file.setSuffix(getFileSuffix(originalName));
        //TODO 这里需要引入 common-io包 和 commons-fileupload
        try {
            File newFile = new File(servletContext.getRealPath(savePath));
            file.setAbsolutePath(newFile.toURI().getPath());
            FileUtils.moveFile(tempFile, newFile);
        } catch (IOException e) {
            throw new RuntimeException("上传文件保存到目标路径["+savePath+"]时发生错误，操作失败");
        }
        return file;
    }

    private static String getFileSuffix(String fileName) {
        int i = fileName.lastIndexOf(".");
        return i > -1 ? fileName.substring(i + 1) : "";
    }

    private static String getFileSavePath(String originalName) {
        String suffix = getFileSuffix(originalName);
        String savePath = "/upload/"+DateKit.format(new Date(), DateKit.DATE_YMD)+"/"+new Date().getTime()+"."+suffix;;
//        if (StringKit.isBlank(savePath)) {
//            throw new RuntimeException("获取上传文件的保存路径失败，上传失败");
//        }
//        String[] patterns = StringKit.matchAll(savePath, "(?<=\\{)[^}]+");
//        for (String p : patterns) {
//            String[] strs = p.split(":");
//            switch (strs[0]) {
//                case "uuid" :
//                    savePath = StringKit.replace(savePath, "{"+p+"}",  UUID.fastUUID().toString(true));
//                    break;
//                case "date":
//                    savePath = StringKit.replaceOnce(savePath, "{"+p+"}",  DateKit.format(new Date(), strs[1]));
//                    break;
//                case "suffix":
//                    String suffix = getFileSuffix(originalName);
//                    savePath = StringKit.replaceOnce(savePath, "{"+p+"}", "".equals(suffix) ?  "_null_" :suffix);
//                    break;
//                case "random":
//                    savePath = StringKit.replaceOnce(savePath, "{"+p+"}", RandomUtil.randomString(Integer.parseInt(strs[1])));
//                    break;
//                case "session":
//                    Object value = ActionContext.getRequest().getSession().getAttribute(strs[1]);
//                    savePath = StringKit.replaceOnce(savePath, "{"+p+"}", value == null ? "_null_":value.toString());
//                    break;
//                case "originalName":
//                    savePath = StringKit.replaceOnce(savePath, "{"+p+"}", originalName);
//                    break;
//
//            }
//        }
        return savePath;
    }

    /**
     * 处理Map参数值
     * @param result
     * @param feature
     * @param paramValues
     * @param remainKey
     * @return
     */
    private static Object processMapParamValue(Object result, ParamDefinition.ParamFeature feature, String[] paramValues, String remainKey) {
        if (result == null) {
            if (feature.getClazz() == Map.class) {
                result = new HashMap<>();
            } else {
                try {
                    result = feature.getClazz().newInstance();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        Map map = (Map) result;

        String key = clean(remainKey);
        Object keyObj = processParamValue(null, feature.getSubParamFeatures()[0], new String[]{key}, "");
        map.put(keyObj, processParamValue(map.get(keyObj), feature.getSubParamFeatures()[1], paramValues,
                remainKey.substring(key.length() + 1)));

        return map;

    }

    /**
     * 处理List字段值
     * @param result
     * @param feature
     * @param paramValues
     * @param remainKey
     * @return
     */
    private static Object processListParamValue(Object result, ParamDefinition.ParamFeature feature, String[] paramValues, String remainKey) {
        if (result == null) {
            if (feature.getClazz() == List.class) {
                result = new ArrayList<>();
            } else {
                try {
                    result = feature.getClazz().newInstance();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }

        List list = (List) result;

        String key = clean(remainKey); //[1].name 可以获取到1
        Integer index = Integer.parseInt(key);
        if (list.size() < index + 1) {
            for (int i = list.size(); i < index + 1; i++) {
                list.add(null);
            }
        }
        list.set(index, processParamValue(list.get(index), feature.getSubParamFeatures()[0], paramValues,
                remainKey.substring(key.length() + 2)));
        return list;
    }



    /**
     * 处理pojo字段值
     * @param result
     * @param feature
     * @param paramValue
     * @param remainKey
     * @return
     */
    private static Object processPojoParamValue(Object result, ParamDefinition.ParamFeature feature, String[] paramValue, String remainKey) {
        if (result == null) {
            try {
                result = feature.getClazz().newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        String key = clean(remainKey); //.user.name 可以获取到 user

        if (StringKit.isNotBlank(key)) {
            for (ParamDefinition.ParamFeature subParamFeature : feature.getSubParamFeatures()) {
                if (subParamFeature.getField().getName().equals(key)) {
                    try {
                        Object value = subParamFeature.getField().get(result);
                        subParamFeature.getField().set(result,
                                processParamValue(value, subParamFeature, paramValue, remainKey.substring(key.length() + 1)));
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                    break;
                }
            }
        }

        return result;


    }

    /**
     * 处理数组元素
     * @param paramValue
     * @param remainKey
     * @return
     */
    private static Object processArrayParamValue(ParamDefinition.ParamFeature feature, String[] paramValue, String remainKey) {
        if (paramValue == null) return null;
        Object array = Array.newInstance(feature.getSubParamFeatures()[0].getClazz(), paramValue.length);

        for (int i = 0; i < paramValue.length; i++) {
            Array.set(array, i, processParamValue(null, feature.getSubParamFeatures()[0], new String[]{paramValue[i]}, ""));

        }

        return array;

    }



}
